package com.practice.communication.nio.gp.channel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;

import com.practice.communication.nio.gp.buffer.Buffers;

/**
 *	服务器端：接收客户端发送过来的数据并显示
 *	服务器把接收到的数据加上“echo from service:”再发送回去
 */
public class ServiceSocketChannelDemo {
	
	private static class TCPEchoServer implements Runnable {
		
		private InetSocketAddress localAddress;

		public TCPEchoServer(int port) {
			this.localAddress = new InetSocketAddress(port);
		}

		@Override
		public void run() {
			Charset utf8 = Charset.forName("UTF-8");
			
			ServerSocketChannel serverChannel = null;
			Selector selector = null;
			
			Random ran = new Random();
			
			try {
				//创建选择器
				selector = Selector.open();
				
				//创建服务端通道
				serverChannel = ServerSocketChannel.open();
				serverChannel.configureBlocking(false);
				
				//设置监听服务端的端口，设置最大连接缓冲数为100
				serverChannel.bind(localAddress, 100);
				
				//服务器通道只能对tcp连接时间感兴趣
				serverChannel.register(selector, SelectionKey.OP_ACCEPT);
			} catch (IOException e) {
				System.out.println("server started failed");
				return;
			}
			
			System.out.println("server started with address : " + localAddress);
			
			//服务端线程被中断后会退出
			try {
				while (!Thread.currentThread().isInterrupted()) {
					int n = selector.select();
					if (n == 0) continue;
					
					Set<SelectionKey> keys = selector.selectedKeys();
					Iterator<SelectionKey> it = keys.iterator();
					SelectionKey key = null;
					
					while(it.hasNext()) {
						key = it.next();
						//防止下次select()方法返回已处理过的通道
						it.remove();
						
						//若发现异常，说明客户端连接出现问题，但服务器要保持正常
						try {
							//serverChannel只能对连接事件感兴趣
							if (key.isAcceptable()) {
								//accept()方法会返回一个普通通道，每个通道在内核中都对应一个socket的缓冲区
								SocketChannel channel = serverChannel.accept();
								channel.configureBlocking(false);
								
								//向选择器注册这个通道和感兴趣的事件，同时提供这个新通道相关的缓冲区
								int interestSet = SelectionKey.OP_READ;
								channel.register(selector, interestSet, new Buffers(256, 256));
								System.out.println("accept from " + channel.getRemoteAddress());
							}
							
							//（普通）通道感兴趣事件且有数据可读
							if (key.isReadable()) {
								//通过SelectionKey获取通道对应的缓冲区
								Buffers buffer = (Buffers)key.attachment();
								ByteBuffer readBuffer = buffer.getReadBuffer();
								ByteBuffer writeBuffer = buffer.getWriteBuffer();
								
								//通过SelectionKey获取对应的通道
								SocketChannel channel = (SocketChannel)key.channel();
								
								//从底层socket读缓冲区中读入数据
								channel.read(readBuffer);
								readBuffer.flip();
								
								//解码显示，客户端发送过来的消息
								CharBuffer charBuffer = utf8.decode(readBuffer);
								System.out.println(charBuffer.toString());
								
								readBuffer.rewind();
								
								//准备好向客户端发送消息，先写入echo from service,再写入收到的消息
								writeBuffer.put("echo from service ".getBytes("UTF-8"));
								writeBuffer.put(readBuffer);
								
								readBuffer.clear();
								
								//设置通道写事件
								key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
							}
							
							//通道感兴趣写事件且底层缓冲区有空闲
							if (key.isWritable()) {
								Buffers buffer = (Buffers)key.attachment();
								
								ByteBuffer writeBuffer = buffer.getWriteBuffer();
								writeBuffer.flip();
								
								SocketChannel channel = (SocketChannel)key.channel();
								
								int len = 0;
								while (writeBuffer.hasRemaining()) {
									len = channel.write(writeBuffer);
									//说明底层的写缓冲区已满
									if (len == 0)
										break;
								}
								
								writeBuffer.compact();
								
								//说明数据全部写入到底层的socket写缓冲区
								if (len != 0) {
									//取消通道的写事件
									key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
								}
							}
						} catch(IOException e) {
							System.out.println("service incounter client error");
							//若客户端连接出现异常，从SelectionKey中移除这个key
							key.cancel();
							key.channel().close();
						}
					}
					
					Thread.sleep(ran.nextInt(500));
				}
			} catch (InterruptedException e) {
				System.out.println("serverThread is isinterrupted");
			} catch (IOException E) {
				System.out.println("serverThread selector error");
			}  finally {
				try {
					selector.close();
				} catch (IOException e) {
					System.out.println("selector closed failed");
				} finally {
					System.out.println("server close");
				}
			}
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		Thread thread = new Thread(new TCPEchoServer(8080));
		thread.start();
		
		Thread.sleep(100000);
		
		thread.interrupt();
	}

}
